View Javadoc
1   package org.apache.maven.surefire.junit4;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import org.apache.maven.surefire.booter.Command;
23  import org.apache.maven.surefire.booter.CommandListener;
24  import org.apache.maven.surefire.booter.CommandReader;
25  import org.apache.maven.surefire.common.junit4.ClassMethod;
26  import org.apache.maven.surefire.common.junit4.JUnit4RunListener;
27  import org.apache.maven.surefire.common.junit4.JUnit4TestChecker;
28  import org.apache.maven.surefire.common.junit4.JUnitTestFailureListener;
29  import org.apache.maven.surefire.common.junit4.Notifier;
30  import org.apache.maven.surefire.providerapi.AbstractProvider;
31  import org.apache.maven.surefire.providerapi.ProviderParameters;
32  import org.apache.maven.surefire.report.ConsoleOutputReceiver;
33  import org.apache.maven.surefire.report.PojoStackTraceWriter;
34  import org.apache.maven.surefire.report.ReportEntry;
35  import org.apache.maven.surefire.report.ReporterFactory;
36  import org.apache.maven.surefire.report.RunListener;
37  import org.apache.maven.surefire.report.SimpleReportEntry;
38  import org.apache.maven.surefire.suite.RunResult;
39  import org.apache.maven.surefire.testset.TestListResolver;
40  import org.apache.maven.surefire.testset.TestRequest;
41  import org.apache.maven.surefire.testset.TestSetFailedException;
42  import org.apache.maven.surefire.util.RunOrderCalculator;
43  import org.apache.maven.surefire.util.ScanResult;
44  import org.apache.maven.surefire.util.TestsToRun;
45  import org.junit.runner.Description;
46  import org.junit.runner.Request;
47  import org.junit.runner.Result;
48  import org.junit.runner.Runner;
49  import org.junit.runner.manipulation.Filter;
50  import org.junit.runner.notification.RunNotifier;
51  import org.junit.runner.notification.StoppedByUserException;
52  
53  import java.util.Collection;
54  import java.util.Set;
55  
56  import static java.lang.reflect.Modifier.isAbstract;
57  import static java.lang.reflect.Modifier.isInterface;
58  import static org.apache.maven.surefire.booter.CommandReader.getReader;
59  import static org.apache.maven.surefire.common.junit4.JUnit4ProviderUtil.generateFailingTests;
60  import static org.apache.maven.surefire.common.junit4.JUnit4Reflector.createDescription;
61  import static org.apache.maven.surefire.common.junit4.JUnit4Reflector.createIgnored;
62  import static org.apache.maven.surefire.common.junit4.JUnit4RunListener.rethrowAnyTestMechanismFailures;
63  import static org.apache.maven.surefire.common.junit4.JUnit4RunListenerFactory.createCustomListeners;
64  import static org.apache.maven.surefire.common.junit4.Notifier.pureNotifier;
65  import static org.apache.maven.surefire.report.ConsoleOutputCapture.startCapture;
66  import static org.apache.maven.surefire.report.SimpleReportEntry.withException;
67  import static org.apache.maven.surefire.testset.TestListResolver.optionallyWildcardFilter;
68  import static org.apache.maven.surefire.util.TestsToRun.fromClass;
69  import static org.junit.runner.Request.aClass;
70  import static org.junit.runner.Request.method;
71  
72  /**
73   * @author Kristian Rosenvold
74   */
75  public class JUnit4Provider
76      extends AbstractProvider
77  {
78      private static final String UNDETERMINED_TESTS_DESCRIPTION = "cannot determine test in forked JVM with surefire";
79  
80      private final ClassLoader testClassLoader;
81  
82      private final String customRunListeners;
83  
84      private final JUnit4TestChecker jUnit4TestChecker;
85  
86      private final TestListResolver testResolver;
87  
88      private final ProviderParameters providerParameters;
89  
90      private final RunOrderCalculator runOrderCalculator;
91  
92      private final ScanResult scanResult;
93  
94      private final int rerunFailingTestsCount;
95  
96      private final CommandReader commandsReader;
97  
98      private TestsToRun testsToRun;
99  
100     public JUnit4Provider( ProviderParameters bootParams )
101     {
102         // don't start a thread in CommandReader while we are in in-plugin process
103         commandsReader = bootParams.isInsideFork() ? getReader().setShutdown( bootParams.getShutdown() ) : null;
104         providerParameters = bootParams;
105         testClassLoader = bootParams.getTestClassLoader();
106         scanResult = bootParams.getScanResult();
107         runOrderCalculator = bootParams.getRunOrderCalculator();
108         customRunListeners = bootParams.getProviderProperties().get( "listener" );
109         jUnit4TestChecker = new JUnit4TestChecker( testClassLoader );
110         TestRequest testRequest = bootParams.getTestRequest();
111         testResolver = testRequest.getTestListResolver();
112         rerunFailingTestsCount = testRequest.getRerunFailingTestsCount();
113     }
114 
115     public RunResult invoke( Object forkTestSet )
116         throws TestSetFailedException
117     {
118         upgradeCheck();
119 
120         ReporterFactory reporterFactory = providerParameters.getReporterFactory();
121 
122         RunResult runResult;
123         try
124         {
125             RunListener reporter = reporterFactory.createReporter();
126 
127             startCapture( (ConsoleOutputReceiver) reporter );
128             // startCapture() called in prior to setTestsToRun()
129 
130             if ( testsToRun == null )
131             {
132                 setTestsToRun( forkTestSet );
133             }
134 
135             Notifier notifier = new Notifier( new JUnit4RunListener( reporter ), getSkipAfterFailureCount() );
136             Result result = new Result();
137             notifier.addListeners( createCustomListeners( customRunListeners ) )
138                 .addListener( result.createListener() );
139 
140             if ( isFailFast() && commandsReader != null )
141             {
142                 registerPleaseStopJUnitListener( notifier );
143             }
144 
145             try
146             {
147                 notifier.fireTestRunStarted( testsToRun.allowEagerReading()
148                                                  ? createTestsDescription( testsToRun )
149                                                  : createDescription( UNDETERMINED_TESTS_DESCRIPTION ) );
150 
151                 if ( commandsReader != null )
152                 {
153                     registerShutdownListener( testsToRun );
154                     commandsReader.awaitStarted();
155                 }
156 
157                 for ( Class<?> testToRun : testsToRun )
158                 {
159                     executeTestSet( testToRun, reporter, notifier );
160                 }
161             }
162             finally
163             {
164                 notifier.fireTestRunFinished( result );
165                 notifier.removeListeners();
166             }
167             rethrowAnyTestMechanismFailures( result );
168         }
169         finally
170         {
171             runResult = reporterFactory.close();
172         }
173         return runResult;
174     }
175 
176     private void setTestsToRun( Object forkTestSet )
177         throws TestSetFailedException
178     {
179         if ( forkTestSet instanceof TestsToRun )
180         {
181             testsToRun = (TestsToRun) forkTestSet;
182         }
183         else if ( forkTestSet instanceof Class )
184         {
185             testsToRun = fromClass( (Class<?>) forkTestSet );
186         }
187         else
188         {
189             testsToRun = scanClassPath();
190         }
191     }
192 
193     private boolean isRerunFailingTests()
194     {
195         return rerunFailingTestsCount > 0;
196     }
197 
198     private boolean isFailFast()
199     {
200         return providerParameters.getSkipAfterFailureCount() > 0;
201     }
202 
203     private int getSkipAfterFailureCount()
204     {
205         return isFailFast() ? providerParameters.getSkipAfterFailureCount() : 0;
206     }
207 
208     private void registerShutdownListener( final TestsToRun testsToRun )
209     {
210         commandsReader.addShutdownListener( new CommandListener()
211         {
212             public void update( Command command )
213             {
214                 testsToRun.markTestSetFinished();
215             }
216         } );
217     }
218 
219     private void registerPleaseStopJUnitListener( final Notifier notifier )
220     {
221         commandsReader.addSkipNextTestsListener( new CommandListener()
222         {
223             public void update( Command command )
224             {
225                 notifier.pleaseStop();
226             }
227         } );
228     }
229 
230     private void executeTestSet( Class<?> clazz, RunListener reporter, Notifier notifier )
231     {
232         final ReportEntry report = new SimpleReportEntry( getClass().getName(), clazz.getName() );
233         reporter.testSetStarting( report );
234         try
235         {
236             executeWithRerun( clazz, notifier );
237         }
238         catch ( Throwable e )
239         {
240             if ( isFailFast() && e instanceof StoppedByUserException )
241             {
242                 String reason = e.getClass().getName();
243                 Description skippedTest = createDescription( clazz.getName(), createIgnored( reason ) );
244                 notifier.fireTestIgnored( skippedTest );
245             }
246             else
247             {
248                 String reportName = report.getName();
249                 String reportSourceName = report.getSourceName();
250                 PojoStackTraceWriter stackWriter = new PojoStackTraceWriter( reportSourceName, reportName, e );
251                 reporter.testError( withException( reportSourceName, reportName, stackWriter ) );
252             }
253         }
254         finally
255         {
256             reporter.testSetCompleted( report );
257         }
258     }
259 
260     private void executeWithRerun( Class<?> clazz, Notifier notifier )
261         throws TestSetFailedException
262     {
263         JUnitTestFailureListener failureListener = new JUnitTestFailureListener();
264         notifier.addListener( failureListener );
265         boolean hasMethodFilter = testResolver != null && testResolver.hasMethodPatterns();
266 
267         try
268         {
269             try
270             {
271                 notifier.asFailFast( isFailFast() );
272                 execute( clazz, notifier, hasMethodFilter ? createMethodFilter() : null );
273             }
274             finally
275             {
276                 notifier.asFailFast( false );
277             }
278 
279             // Rerun failing tests if rerunFailingTestsCount is larger than 0
280             if ( isRerunFailingTests() )
281             {
282                 Notifier rerunNotifier = pureNotifier();
283                 notifier.copyListenersTo( rerunNotifier );
284                 for ( int i = 0; i < rerunFailingTestsCount && !failureListener.getAllFailures().isEmpty(); i++ )
285                 {
286                     Set<ClassMethod> failedTests = generateFailingTests( failureListener.getAllFailures() );
287                     failureListener.reset();
288                     if ( !failedTests.isEmpty() )
289                     {
290                         executeFailedMethod( rerunNotifier, failedTests );
291                     }
292                 }
293             }
294         }
295         finally
296         {
297             notifier.removeListener( failureListener );
298         }
299     }
300 
301     public Iterable<Class<?>> getSuites()
302     {
303         testsToRun = scanClassPath();
304         return testsToRun;
305     }
306 
307     private TestsToRun scanClassPath()
308     {
309         final TestsToRun scannedClasses = scanResult.applyFilter( jUnit4TestChecker, testClassLoader );
310         return runOrderCalculator.orderTestClasses( scannedClasses );
311     }
312 
313     private void upgradeCheck()
314         throws TestSetFailedException
315     {
316         if ( isJUnit4UpgradeCheck() )
317         {
318             Collection<Class<?>> classesSkippedByValidation =
319                 scanResult.getClassesSkippedByValidation( jUnit4TestChecker, testClassLoader );
320             if ( !classesSkippedByValidation.isEmpty() )
321             {
322                 StringBuilder reason = new StringBuilder();
323                 reason.append( "Updated check failed\n" );
324                 reason.append( "There are tests that would be run with junit4 / surefire 2.6 but not with [2.7,):\n" );
325                 for ( Class testClass : classesSkippedByValidation )
326                 {
327                     reason.append( "   " );
328                     reason.append( testClass.getName() );
329                     reason.append( "\n" );
330                 }
331                 throw new TestSetFailedException( reason.toString() );
332             }
333         }
334     }
335 
336     static Description createTestsDescription( Iterable<Class<?>> classes )
337     {
338         // "null" string rather than null; otherwise NPE in junit:4.0
339         Description description = createDescription( "null" );
340         for ( Class<?> clazz : classes )
341         {
342             description.addChild( createDescription( clazz.getName() ) );
343         }
344         return description;
345     }
346 
347     private static boolean isJUnit4UpgradeCheck()
348     {
349         return System.getProperty( "surefire.junit4.upgradecheck" ) != null;
350     }
351 
352     private static void execute( Class<?> testClass, Notifier notifier, Filter filter )
353     {
354         final int classModifiers = testClass.getModifiers();
355         if ( !isAbstract( classModifiers ) && !isInterface( classModifiers ) )
356         {
357             Request request = aClass( testClass );
358             if ( filter != null )
359             {
360                 request = request.filterWith( filter );
361             }
362             Runner runner = request.getRunner();
363             if ( countTestsInRunner( runner.getDescription() ) != 0 )
364             {
365                 runner.run( notifier );
366             }
367         }
368     }
369 
370     private void executeFailedMethod( RunNotifier notifier, Set<ClassMethod> failedMethods )
371         throws TestSetFailedException
372     {
373         for ( ClassMethod failedMethod : failedMethods )
374         {
375             try
376             {
377                 Class<?> methodClass = Class.forName( failedMethod.getClazz(), true, testClassLoader );
378                 String methodName = failedMethod.getMethod();
379                 method( methodClass, methodName ).getRunner().run( notifier );
380             }
381             catch ( ClassNotFoundException e )
382             {
383                 throw new TestSetFailedException( "Unable to create test class '" + failedMethod.getClazz() + "'", e );
384             }
385         }
386     }
387 
388     /**
389      * JUnit error: test count includes one test-class as a suite which has filtered out all children.
390      * Then the child test has a description "initializationError0(org.junit.runner.manipulation.Filter)"
391      * for JUnit 4.0 or "initializationError(org.junit.runner.manipulation.Filter)" for JUnit 4.12
392      * and Description#isTest() returns true, but this description is not a real test
393      * and therefore it should not be included in the entire test count.
394      */
395     private static int countTestsInRunner( Description description )
396     {
397         if ( description.isSuite() )
398         {
399             int count = 0;
400             for ( Description child : description.getChildren() )
401             {
402                 if ( !hasFilteredOutAllChildren( child ) )
403                 {
404                     count += countTestsInRunner( child );
405                 }
406             }
407             return count;
408         }
409         else if ( description.isTest() )
410         {
411             return hasFilteredOutAllChildren( description ) ? 0 : 1;
412         }
413         else
414         {
415             return 0;
416         }
417     }
418 
419     private static boolean hasFilteredOutAllChildren( Description description )
420     {
421         String name = description.getDisplayName();
422         // JUnit 4.0: initializationError0; JUnit 4.12: initializationError.
423         if ( name == null )
424         {
425             return true;
426         }
427         else
428         {
429             name = name.trim();
430             return name.startsWith( "initializationError0(org.junit.runner.manipulation.Filter)" )
431                            || name.startsWith( "initializationError(org.junit.runner.manipulation.Filter)" );
432         }
433     }
434 
435     private Filter createMethodFilter()
436     {
437         TestListResolver methodFilter = optionallyWildcardFilter( testResolver );
438         return methodFilter.isEmpty() || methodFilter.isWildcard() ? null : new TestResolverFilter( methodFilter );
439     }
440 }